﻿// -----------------------------------------------------------------------
// <copyright file="CrudAsyncTests.cs">
// Copyright (c) 2013 Andrey Veselov. All rights reserved.
// License:  Microsoft Public License (MS-PL)
// Contacts: http://andrey.moveax.com  andrey@moveax.com
// </copyright>
// -----------------------------------------------------------------------

namespace Moveax.DataAccess.Cqrs.Highway.EF.UnitTests
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Threading.Tasks;
	using Microsoft.VisualStudio.TestTools.UnitTesting;
	using Moveax.DataAccess.Cqrs.Highway.EF.UnitTests.DataAccess.Database.Dto;
	using Moveax.DataAccess.Cqrs.Highway.EF.UnitTests.Model.Commands.Users;
	using Moveax.DataAccess.Cqrs.Highway.EF.UnitTests.Model.Queries.Links;
	using Moveax.DataAccess.Cqrs.Highway.EF.UnitTests.Model.Queries.Users;
	using Moveax.DataAccess.Cqrs.Highway.UnitTests.Model;
	using Moveax.DataAccess.Cqrs.Highway.UnitTests.Model.Queries;

	[TestClass]
	public class CrudAsyncTests : TestClassBase
	{
		[TestMethod]
		public async Task EFGetExistingObjectAsync()
		{
			// Arrange 
			User profile = null;

			// Act
			using (var unitOfWork = this._unitOfWorkFactory.Create())
				profile = await unitOfWork.ResolveQuery<IGetUserByIdAsyncQuery>().Execute(this._sampleData.Id, includeLinks: false);

			// Assert
			Assert.IsNotNull(profile);
			Assert.AreEqual(this._sampleData.Id, profile.Id);
			Assert.AreEqual(this._sampleData.Name, profile.Name);
			Assert.AreEqual(this._sampleData.Email, profile.Email);
			Assert.AreEqual(this._sampleData.IsDisabled, profile.IsDisabled);
		}

		[TestMethod]
		public async Task EFGetNonexistentObjectAsync()
		{
			// Arrange 
			User profile = null;

			// Act
			using (var unitOfWork = this._unitOfWorkFactory.Create())
				profile = await unitOfWork.ResolveQuery<IGetUserByIdAsyncQuery>().Execute(int.MaxValue, includeLinks: false);

			// Assert
			Assert.IsNull(profile);
		}

		[TestMethod]
		public async Task EFGetObjectIncludingNavigationPropertiesAsync()
		{
			// Arrange 
			User profile = null;

			// Act
			using (var unitOfWork = this._unitOfWorkFactory.Create())
				profile = await unitOfWork.ResolveQuery<IGetUserByIdAsyncQuery>().Execute(this._sampleData.Id, includeLinks: true);

			// Assert
			Assert.IsNotNull(profile);
			Assert.AreEqual(this._sampleData.Id, profile.Id);
			Assert.AreEqual(this._sampleData.Name, profile.Name);
			Assert.AreEqual(this._sampleData.Email, profile.Email);
			Assert.AreEqual(this._sampleData.IsDisabled, profile.IsDisabled);

			Assert.IsNotNull(profile.Links);
			Assert.AreEqual(this._sampleData.Links.Count(), profile.Links.Count());

			bool allLinksWasFound = profile.Links
				.Select(l => this._sampleData.Links.Any(e => (e.Id == l.Id)))
				.Aggregate(true, (acc, val) => acc && val);

			Assert.IsTrue(allLinksWasFound);
		}

		[TestMethod]
		public async Task EFExcecuteCommandAsync()
		{
			// Arrange
			const string name = "John Doe";
			const string email = "johndoe@unknownserver.doe";
			const bool isDisabled = false;
			var links = new[] { new Link() { Title = "SomeTitle", Url = @"http://somesite.com/" } };

			User savedData = null;

			var cmd = new CreateUserCommand(name, email, isDisabled, links);

			// Act
			await this._unitOfWorkFactory.ExecuteSingleCommandAsync(cmd);

			using (var unitOfWork = this._unitOfWorkFactory.Create())
				savedData = await unitOfWork.ResolveQuery<IGetUserByIdAsyncQuery>().Execute(cmd.Id, includeLinks: true);

			// Assert
			Assert.IsNotNull(savedData);
			Assert.AreEqual(cmd.Id, savedData.Id);
			Assert.AreEqual(name, savedData.Name);
			Assert.AreEqual(email, savedData.Email);
			Assert.AreEqual(isDisabled, savedData.IsDisabled);

			Assert.IsNotNull(savedData.Links);
			Assert.AreEqual(links.Count(), savedData.Links.Count());

			var link = savedData.Links.First();
			Assert.AreEqual(savedData.Id, link.UserProfileId);
			Assert.AreEqual(links[0].Title, link.Title);
			Assert.AreEqual(links[0].Url, link.Url);
		}

		[TestMethod]
		public async Task EFExecuteAdvancedCommandAsync()
		{
			// Arrange 
			User user = null;
			const string newName = "John Doe";

			// NOTE: handler for this command should update login only (without reading entire entity)
			var cmd = new UpdateUserNameCommand(this._sampleData.Id, newName);

			// Act
			await this._unitOfWorkFactory.ExecuteSingleCommandAsync(cmd);

			user = this._unitOfWorkFactory
				.ExecuteSingleQuery<IGetUserByIdQuery, User>(q => q.Execute(cmd.Id, includeLinks: false));

			// Assert
			Assert.IsNotNull(user);
			Assert.AreEqual(this._sampleData.Id, user.Id);
			Assert.AreEqual(newName, user.Name);
			Assert.AreEqual(this._sampleData.IsDisabled, user.IsDisabled);
		}

		[TestMethod]
		public async Task EFExecuteQueriesAtSameTimeAsync()
		{
			// Arrange 
			User user = null;
			IEnumerable<Link> links = null;

			// Act
			using (var unitOfWork = this._unitOfWorkFactory.Create()) {
				Task<User> query1 = unitOfWork
					.ResolveQuery<IGetUserByIdAsyncQuery>()
					.Execute(this._sampleData.Id, includeLinks: true);

				Task<IEnumerable<Link>> query2 = unitOfWork
					.ResolveQuery<IGetLinksByUserIdAsyncQuery>()
					.Execute(this._sampleData.Id);

				await TaskEx.WhenAll(query1, query2);

				user = query1.Result;
				links = query2.Result;
			}

			// Assert
			Assert.IsNotNull(user);
			Assert.IsNotNull(user.Links);
			Assert.IsNotNull(links);

			Assert.AreEqual(user.Links.Count(), links.Count());

			bool allLinksWasFound = user.Links
				.Select(l => links.Any(e => (e.Id == l.Id)))
				.Aggregate(true, (acc, val) => acc && val);

			Assert.IsTrue(allLinksWasFound);
		}

		[TestMethod]
		public async Task EFExecuteQueriesAsync()
		{
			// Arrange 
			User user = null;
			IEnumerable<Link> links = null;

			// Act
			using (var unitOfWork = this._unitOfWorkFactory.Create()) {
				user = await unitOfWork
					.ResolveQuery<IGetUserByIdAsyncQuery>()
					.Execute(this._sampleData.Id, includeLinks: true)
					.ConfigureAwait(false);

				links = await unitOfWork
					.ResolveQuery<IGetLinksByUserIdAsyncQuery>()
					.Execute(this._sampleData.Id)
					.ConfigureAwait(false);
			}

			// Assert
			Assert.IsNotNull(user);
			Assert.IsNotNull(user.Links);
			Assert.IsNotNull(links);

			Assert.AreEqual(user.Links.Count(), links.Count());

			bool allLinksWasFound = user.Links
				.Select(l => links.Any(e => (e.Id == l.Id)))
				.Aggregate(true, (acc, val) => acc && val);

			Assert.IsTrue(allLinksWasFound);
		}

		[TestMethod]
		public async Task EFExecuteCommandsAsync()
		{
			// Arrange 
			const string email = "johndoe@unknownserver.doe";
			const int insertedObjectsCount = 20;

			using (var unitOfWork = this._unitOfWorkFactory.Create()) {
				this.InsertObjectsIntoRepository(unitOfWork, insertedObjectsCount);
			}

			// Act
			var users = this._unitOfWorkFactory.ExecuteSingleQuery<IGetAllQuery, IEnumerable<UserDto>>(q => q.Execute<UserDto>());

			using (var unitOfWork = this._unitOfWorkFactory.Create()) {
				foreach (var user in users) {
					var cmd = new UpdateUserEmailCommand(user.Id, email);
					await unitOfWork.ExecuteCommandAsync(cmd);
				}

				await unitOfWork.CommitAsync();
			}

			users = this._unitOfWorkFactory.ExecuteSingleQuery<IGetAllQuery, IEnumerable<UserDto>>(q => q.Execute<UserDto>());

			// Assert
			Assert.IsNotNull(users);
			Assert.IsTrue(users.All(u => string.Equals(u.Email, email, StringComparison.Ordinal)));
		}
	}
}